#pragma once
#include <lcir/IR/Context.h>
#include <lcir/IR/Dialect.h>
#include <lcir/IR/Slot.h>
#include <lcir/IR/SlotCombiner.h>

#include <optional>
#include <stdexcept>
#include <string_view>

namespace lcir {
class Type;
class Value;
class ProtoInstance {
  friend class GlobalContext;

 public:
  ProtoInstance() = default;
  template <class ProtoTy>
  static ProtoInstance *get() {
    auto *context = GlobalContext::get();
    return context->getProtoInstance<ProtoTy>();
  }
};

class Prototype {
  // TODO: 考虑将name放到ProtoInstance中，这样可以节省把name复制到到处都是的的内存
  std::string name{""};
  std::string info{""};
  Dialect *dialect{nullptr};
  Type *type{nullptr};
  ProtoInstance *instance{nullptr};

  // 和持有原型的value双向引用
  Value* val{nullptr};

 protected:
  Prototype(std::string_view name, Dialect *dialect, Type *typeRef, ProtoInstance *instance)
      : name(name), dialect(dialect), type(typeRef), instance(instance) {}

 public:
  Type *getType() const { return type; }
  ProtoInstance *getInstance() const { return instance; }
  template <class OtherType>
  bool isa() const {
    return getInstance() == ProtoInstance::get<OtherType>();
  }
  template <class OtherType>
  OtherType *dyn_cast() {
    if (!isa<OtherType>()) return nullptr;
    return static_cast<OtherType *>(this);
  }
  template <class OtherType>
  const OtherType *dyn_cast() const {
    if (!isa<OtherType>()) return nullptr;
    return static_cast<const OtherType *>(this);
  }
  template <class OtherType>
  OtherType *cast() {
    if (!isa<OtherType>()) throw std::runtime_error("invalid cast!");
    return static_cast<OtherType *>(this);
  }
  template <class OtherType>
  const OtherType *cast() const {
    if (!isa<OtherType>()) throw std::runtime_error("invalid cast!");
    return static_cast<const OtherType *>(this);
  }

  std::string getName() const {
    std::stringstream ss;
    ss << dialect->getName() << "." << name;
    return ss.str();
  }
  
  // 如果是constProto重写这个方法
  virtual std::optional<std::string> getConstStr() const { return std::nullopt; }

  void setInfo(std::string_view str)  { info = str; }
  std::string getInfo() const { return info; }

  void setValue(Value *value) { val = value; }
  Value* getValue() const { return val; }

  Dialect *getDialect() const { return dialect; }
  virtual ~Prototype() = default;
};

template <class... SlotTy>
class PrototypeTrait {
 public:
  using slotCombinerTy = SlotCombiner<SlotTy...>;
};

template <class DerivedTy, class DialectTy, class... SlotTy>
class PrototypeCRTP : public Prototype {
  using trait = PrototypeTrait<SlotTy...>;
  typename trait::slotCombinerTy slots;

 public:
  PrototypeCRTP() = delete;
  PrototypeCRTP(std::string_view name, Type *typeRef)
      : Prototype(name, DialectTy::get(), typeRef, ProtoInstance::get<DerivedTy>()) {}
  ~PrototypeCRTP() override = default;

  template <class SlotT>
  SlotT *getSlot() {
    return slots.template getSlot<SlotT>();
  }
};

}  // namespace lcir